import { Callout } from '@theguild/components'

# Plugin Lifecycle

Plugins are executed in order of their usage, and inject functionality serially, so aim to keep your
plugins simple and standalone as much as possible.

The `before` functions are called in order, and then the `after` function of each hook will be
called in the same order when the phase has been done.

The basic API allows you to keep the JS closure between `before` and `after`, for example:

```ts
const myPlugin = {
  onParse({ params }) {
    // This is the before function

    return ({ result }) => {
      // The is the after function,
      // JS Closure allow me to access the params here as well
    }
  }
}
```

<Callout>
  All plugin lifecycle methods are executed in FIFO order (First-in, First-out) to make timing more
  consistent.
</Callout>

### The Improved `context`

While in regular GraphQL executions, the `context` is built right before `execute` happens, in
`envelop` we allow plugin developers to take part it that context from all phases.

While calling `getEnveloped` function (the result of `envelop({ plugins: [ ... ]}))`, you can pass
any custom object that will be the base for your GraphQL execution `context`.

In most cases, you'll pass the incoming HTTP request (or, just the relevant parts of it) to make it
available for the plugins you use:

```ts
import * as GraphQLJS from 'graphql'
import { envelop, useEngine } from '@envelop/core'

const getEnveloped = envelop({
  plugins: [
    useEngine(GraphQLJS)
    /* ... plugins ... */
  ]
})

myHttpServer.on('request', async req => {
  const { parse, validate, contextFactory, execute, schema } = getEnveloped({ req })
  // ...
})
```

Plugins might also use this `context` to keep a contextual reference for objects they might need
across stages.

So for example, if you wish to make some data from `parse` stage available for you in `execute`
phase, you can extend the context while running `onParse` and then access that data in `onExecute`:

```ts
const myPlugin = {
  onParse({ extendContext }) {
    extendContext({
      myVar: 'test'
    })
  },
  onExecute({ args }) {
    const myVar = args.contextValue.myVar
  }
}
```

## Plugins API

You can find the
[complete signature for plugins API here](https://github.com/graphql-hive/envelop/blob/d4c8f90f9fbccf3032b3b3b05f359b691076d25b/packages/types/src/index.ts#L55).

### `onPluginInit(api)`

This method is called only once when the plugin is being initialized. This hook is triggered only
once and is useful for setting up the plugin.

#### API

- `setSchema` - sets the initial schema to be used.
- `plugins` - list of all other loaded plugins.
- `addPlugin` - adds a plugin to the list of plugins.

### `onEnveloped(api)`

This method is called every time `getEnveloped` is called, and per request. This is useful if you
need to do some setup right before an incoming execution flow.

#### API

- `setSchema` - sets the initial schema to be used.
- `context` - the initial context object passed to `getEnveloped`, including the context built by
  plugins that ran before.
- `extendContext` - extends the initial context object with additional fields.

### `onParse(api)`

Called every time an operation is being executed.

#### `before`

- `params` - the original parameters passes to `parse` function.
- `parseFn` - the current `parse` function. By default, it's the one from `graphql` package.
- `setParseFn` - Replaces the `parse` function with a custom function.
- `setParsedDocument` - sets the parse result. Setting this will skip calling `parse` for the
  executed operation.
- `context` - the context object built so far by other plugins.
- `extendContext` - extends the context object with additional fields.

#### `after`

- `result` - the result `DocumentNode` of the parsing function.
- `replaceParseResult` - replaces the parsed result.
- `context` - the context object built so far by other plugins.
- `extendContext` - extends the context object with additional fields.

### `onValidate(api)`

Called every time an operation is being executed.

#### `before`

- `params` - the original parameters passes to `validate` function.
- `validateFn` - the current `validate` function. By default, it's the one from `graphql` package.
- `setValidationFn` - Replaces the `validate` function with a custom function.
- `addValidationRule` - Adds a validation rule to the list of default validation rules (as defined
  in `graphql` package).
- `setResult` - sets the validation result. Setting this will skip calling `validate` for the
  executed operation.
- `context` - the context object built so far by other plugins.
- `extendContext` - extends the context object with additional fields.

#### `after`

- `valid` - A boolean indicates if the validation passed.
- `result` - `null` in case of a valid document, otherwise an array of validation errors.
- `context` - the context object built so far by other plugins.
- `extendContext` - extends the context object with additional fields.

### `onContext(api)`

Called every time an operation is being executed. Used for building the GraphQL context
incrementally.

#### `before`

- `context` - the context object built so far by other plugins.
- `extendContext` - extends the context object with additional fields.

#### `after`

- `context` - the eventual built context, by all plugins.
- `extendContext` - extends the context object with additional fields.

### `onExecute(api)`

Called every time an operation is being executed. The return value of this function is extended and
allows you to hook into resolver calls if needed.

#### `before`

- `args` - arguments pass to `execute` (contains the document, variables and everything else that is
  needed for executing the GraphQL operation)
- `executeFn` - the `execute` function to use, by default it's the one from `graphql` package.
- `setExecuteFn` - replaces the `execute` function.
- `setResultAndStopExecution` - sets the result of the execution immediately. Calling this function
  will stop execution.
- `extendContext` - allow you to extend the context before executing the operation.

You can return an `object` from that function, with the following fields:

#### `onExecuteDone`

Triggered when the execution of the operation is done.

- `result` - the execution result, or AsyncIterable in case of stream response.
- `setResult` - replaces the result. can either be with data or errors.

Since `envelop` aims to support stream responses (for live queries, or `@stream/@defer`), the
`result` might be an `AsyncIterable` of multiple execution results.

If you wish your plugin to support this, please make sure to use the
`handleStreamOrSingleExecutionResult` helper, like that:

```ts
import { handleStreamOrSingleExecutionResult, Plugin } from '@envelop/types'

const myPlugin = (): Plugin => {
  return {
    onExecute({ args }) {
      return {
        onExecuteDone(payload) {
          return handleStreamOrSingleExecutionResult(payload, ({ result, setResult }) => {
            // Here you can access the result, and modify it with setResult if needed
          })
        }
      }
    }
  }
}
```

Alternately, if you don't need to support stream responses, you can use the `isAsyncIterable`
function as a type-guard:

```ts
import { isAsyncIterable, Plugin } from '@envelop/types'

const myPlugin = (): Plugin => {
  return {
    onExecute({ args }) {
      return {
        onExecuteDone({ result, setResult }) {
          if (!isAsyncIterable(result)) return
          // Here you can access result, and modify it with setResult if needed
        }
      }
    }
  }
}
```

### `onSubscribe(api)`

Called every time a `subscription` operation is being executed. The return value of this function is
extended, and allow you to hooks into resolver calls if needed.

#### `before`

- `args` - arguments passes to `subscribe` (contains the document, variables and everything else
  that needed for executing the GraphQL operation)
- `subscribeFn` - the `subscribe` function to use, by default it's the one from `graphql` package.
- `setSubscribeFn` - replaces the `subscribe` function.
- `extendContext` - allow you to extend the context before executing the operation.
- `setResultAndStopExecution` - sets the result of the execution immediately. Calling this function
  will stop execution.

You can return an `object` from that function, with the following fields:

#### `onSubscribeResult`

Triggered when subscription result is being emitted from a `subscription` execution.

- `result` - the subscription result.
- `setResult` - replaces the result. can either be with data or errors.

### `onSchemaChange(api)`

`envelop` allow you to manage a reference to a schema, that you can later access and use within your
server.

Some plugins (like gateway implementations) could potentially change the schema while running, so
`envelop` will trigger that event in case of a schema change _after all plugins have initialized_.

#### API

- `schema` - the `GraphQLSchema`
- `replaceSchema` - replaces the schema. Calling this will trigger `onSchemaChange` for all other
  plugins (except for the one that initiated the change);
